package gu.sql2java.excel;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Primitives;

import gu.sql2java.BaseRow;
import gu.sql2java.SimpleLog;
import gu.sql2java.UnnameRow;
import gu.sql2java.excel.annotations.ExcelHandlerAdapter;
import gu.sql2java.excel.config.MapExpression;
import gu.sql2java.excel.config.PropertyConfig;
import gu.sql2java.excel.config.SheetConfig;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static net.gdface.utils.BeanPropertyUtils.isEmpty;

/**
 * 基于apache POI实现数据记录导出为Excel的一页(工作薄)
 * @author guyadong
 * @since 3.13.0
 *
 * @param <B> 数据记录对象类型(Map,Java Bean)
 */
public class SheetGenerator<B> extends BaseExcelWriter{

	private Iterable<B> rows;
	private Workbook workbook;
	private Sheet sheet;
	private SheetConfig sheetConfig = new SheetConfig();
	private final Map<String, CellStyle> styles = Maps.newHashMap();
    /**
     * 当前行号
     */
    private int rownum;
	private List<PropertyConfig> exportedColumnConfigs;
	/**
	 * 自动调整列宽的行数限制，行数超过此值不调整列宽
	 */
	private int rowLimitForAutoColumnSize = 10000;
	
	/**
	 * 构造方法
	 * @param rows 数据记录对象列表
	 */
	@SuppressWarnings("unchecked")
	protected SheetGenerator(Iterable<B> rows) {
		Type superClass = getClass().getGenericSuperclass();
		Class<B> beanClass = (Class<B>) getRawClass(((ParameterizedType) superClass).getActualTypeArguments()[0]);
		init(rows, beanClass, null);
	}
	protected SheetGenerator() {
		this(null);
	}
	/**
	 * @param rows 数据记录对象列表
	 * @param beanClass 数据记录类型(Map,Java Bean)
	 * @param includeCoumns 输出字段白名单,只输出在名单内的字段，为{@code null}或空输出所有字段,
	 * 										  Map类型记录无法预知类型的字段完整信息,所以对于Map类型的记录,如果不指定此字段，
	 *                                        则要对{@code Iterable}中所有的Map记录遍历，才能获取完整的字段信息，效率较低
	 *                                        所以建议对于Map类型记录要定义此参数
	 */
	public SheetGenerator(Iterable<B> rows,Class<B> beanClass, Iterable<String> includeColumns) {
		init(rows, beanClass, includeColumns);
	}
	/**
	 * @param rows 数据记录对象列表
	 * @param beanClass 数据记录类型(Map,Java Bean)
	 * @param includeCoumns 输出字段白名单,只输出在名单内的字段，为{@code null}或空输出所有字段,
	 * 										  Map类型记录无法预知类型的字段完整信息,所以对于Map类型的记录,如果不指定此字段，
	 *                                        则要对{@code Iterable}中所有的Map记录遍历，才能获取完整的字段信息，效率较低
	 *                                        所以建议对于Map类型记录要定义此参数
	 */
	public SheetGenerator(Iterable<B> rows,Class<B> beanClass, String... includeColumns) {
		this(rows, beanClass, null == includeColumns? Collections.emptyList() : Arrays.asList(includeColumns));
	}
	/**
	 * 构造方法
	 * @param beanClass 数据记录类型(Map,Java Bean)
	 * @param includeCoumns 输出字段白名单,只输出在名单内的字段，为{@code null}或空输出所有字段,
	 * 										  Map类型记录无法预知类型的字段完整信息,所以对于Map类型的记录,如果不指定此字段，
	 *                                        则要对{@code Iterable}中所有的Map记录遍历，才能获取完整的字段信息，效率较低
	 *                                        所以建议对于Map类型记录要定义此参数
	 */
	public SheetGenerator(Class<B> beanClass, Iterable<String> includeColumns) {
		this((Object)null, beanClass, includeColumns);
	}
	/**
	 * @param rows 数据记录对象列表
	 * @param beanClass 数据记录类型(Map,Java Bean)
	 * @param includeCoumns 输出字段白名单,只输出在名单内的字段，为{@code null}或空输出所有字段,
	 * 										  Map类型记录无法预知类型的字段完整信息,所以对于Map类型的记录,如果不指定此字段，
	 *                                        则要对{@code Iterable}中所有的Map记录遍历，才能获取完整的字段信息，效率较低
	 *                                        所以建议对于Map类型记录要定义此参数
	 */
	@SuppressWarnings("unchecked")
	public SheetGenerator(Object rows,Class<B> beanClass, Iterable<String> includeColumns) {
		Iterable<B> input;
		if(null == rows){
			input = Collections.emptyList();
		}else if(rows.getClass().isArray()) {
			input = Arrays.asList((B[])rows); 
		}else if(rows instanceof Iterable){
			input = (Iterable<B>)rows;
		}else {
			throw new IllegalArgumentException("invalid rows type " + rows.getClass().getName());
		}
		init(input, beanClass, includeColumns);
	}
	/**
	 * 构造方法
	 * @param iterable             数据记录对象列表
	 * @param includeCoumns 输出字段白名单,只输出在名单内的字段，为{@code null}或空输出所有字段,
	 * 										  Map类型记录无法预知类型的字段完整信息,所以对于Map类型的记录,如果不指定此字段，
	 *                                        则要对{@code Iterable}中所有的Map记录遍历，才能获取完整的字段信息，效率较低
	 *                                        所以建议对于Map类型记录要定义此参数
	 */
	@SuppressWarnings({ "unchecked" })
	protected SheetGenerator(Iterable<B> iterable,Iterable<String> includeCoumns){
		Type superClass = getClass().getGenericSuperclass();
		Class<B> beanClass = (Class<B>) getRawClass(((ParameterizedType) superClass).getActualTypeArguments()[0]);
		init(iterable, beanClass, includeCoumns);
	}
	/**
	 * 构造方法
	 * @see #ExcelGenerator(Iterable, Iterable)
	 */
	protected SheetGenerator(Iterable<B> iterable,String... includeCoumns){
		this(iterable,null == includeCoumns ? null : Arrays.asList(includeCoumns));
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void init(Iterable<B> rows,Class<B> beanClass, Iterable<String> includeColumns){
		Iterable<B> list;
		if(rows instanceof BlockingQueue){
		    /** 从队列中获取第一个元素用于初始化 sheetConfig */
		    list = peek((BlockingQueue)rows,(long)sheetConfig.getQueueTimeout(),TimeUnit.SECONDS);
		    this.rows = rows;
		}else {
		    this.rows = Iterables.filter(firstNonNull(rows,Collections.emptyList()), Predicates.notNull());
		    list = this.rows;
        }
		if(!Iterables.isEmpty(list) && UnnameRow.class.isAssignableFrom(list.iterator().next().getClass())){
			BaseRow first = ((BaseRow)list.iterator().next());
			this.sheetConfig = new SheetConfig(first.fetchMetaData());
		}else if(Map.class.isAssignableFrom(beanClass)){
			this.sheetConfig = new SheetConfig((Iterable<Map>)list,includeColumns);
		}else{
			this.sheetConfig = new SheetConfig(beanClass);
			this.sheetConfig.setIncludeColumns(includeColumns);
		}
	}
	/**
	 * call {@link BlockingQueue#peek()} to retrieve first element from queue,waiting up to the specified wait time if necessary for an element to become available.
	 * @param queue
	 * @param timeout how long to wait before giving up, in units of unit
	 * @param unit a TimeUnit determining how to interpret the timeout parameter
	 * @return the head of this queue, or null if the specified waiting time elapses before an element is available
	 */
	@SuppressWarnings("unchecked")
    private List<B> peek(BlockingQueue<?> queue,long timeout,TimeUnit unit){
	    long intervalMills = 100;
	    long timeoutMills = (TimeUnit.MILLISECONDS.convert(timeout, unit) + intervalMills -1)/intervalMills * intervalMills;
	    Object obj;
	    for(;null == (obj = queue.peek()) && timeoutMills > 0; timeoutMills -= intervalMills){
	        try {
	        	Thread.sleep(intervalMills);
            } catch (InterruptedException e) {
            	break;
            }
	    }
	    if(null == obj){
	        return Collections.emptyList();
	    }else if(obj instanceof List){
	        return (List<B>) obj;
	    }else if(obj.getClass().isArray()){
	        return Arrays.asList((B[])obj);
        }else {
            return (List<B>) Arrays.asList(obj);
        }
	}
    private static Class<?> getRawClass(Type type){
        if(type instanceof Class<?>){
            return (Class<?>) type;
        } else if(type instanceof ParameterizedType){
            return getRawClass(((ParameterizedType) type).getRawType());
        } else{
            throw new IllegalArgumentException("invalid type");
        }
    }
    private void createCell(Row row, int columnCount, Object value, CellStyle style) {
		Cell cell = row.createCell(columnCount);
		if (value instanceof Integer) {
			cell.setCellValue((Integer) value);
		} else if (value instanceof Long) {
			cell.setCellValue((Long) value);
		} else if (value instanceof Double) {
			cell.setCellValue((Double) value);
		} else if (value instanceof Boolean) {
			cell.setCellValue((Boolean) value);
		} else if (value instanceof Date) {
			cell.setCellValue((Date) value);
		} else if (value instanceof LocalDateTime) {
			cell.setCellValue((LocalDateTime) value);
		} else if (value instanceof LocalDate) {
			cell.setCellValue((LocalDate) value);
		} else if (value instanceof Calendar) {
			cell.setCellValue((Calendar) value);
		} else if (value instanceof RichTextString) {
			cell.setCellValue((RichTextString) value);
		} else {
			cell.setCellValue(null == value ? null :String.valueOf(value));
		}
		cell.setCellStyle(style);
	}
    /**
     * 创建sheet标题
     */
    private void createTitle()
    {
    	rownum = 0;
        if (!isNullOrEmpty(sheetConfig.getTitle()))
        {
            Row titleRow = sheet.createRow(rownum++);
            //titleRow.setRowStyle(styles.get("title"));
            Cell titleCell = titleRow.createCell(0);
            titleCell.setCellStyle(styles.get("title"));
            titleCell.setCellValue(sheetConfig.getTitle());
            if(exportedColumnConfigs.size()>1){
            	sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 
            			0,
            			this.exportedColumnConfigs.size() - 1));
            }
        }
    }
	/**
	 * 写入标题行及(第一行)字段名
	 * @param sheet 
	 */
	private void writeHeader(Sheet sheet) {
		Row row = sheet.createRow(rownum++);
        AtomicInteger columnCount = new AtomicInteger(0);
        if(!exportedColumnConfigs.isEmpty()){
            exportedColumnConfigs.forEach(c->{
                createCell(row, columnCount.getAndIncrement(), c.getExcelColumnName(), styles.get("header"));
            });
        }
	}
	/**
     * 写入标题行及(第一行)字段名
     * @param csvPrinter 
     * @throws IOException 
     */
    private void writeHeader(CSVPrinter csvPrinter) throws IOException {
        for(PropertyConfig c:exportedColumnConfigs){
            csvPrinter.print(c.getExcelColumnName());
        }
        csvPrinter.println();
    }
	/**
	 * 写入一条记录
	 * @param record
	 * @param sheet
	 */
	private void writeRow(B record, Sheet sheet) {
	    Row row = sheet.createRow(rownum++);
        for(int columnCount = 0; columnCount<exportedColumnConfigs.size(); ++columnCount){
            PropertyConfig config = exportedColumnConfigs.get(columnCount);
            Object value = readFrom(config,record);
            createCell(row, columnCount, value, styleOf(config, value));
        }
	}
	/**
	 * 写入一条记录
	 * @param record
	 * @param csvPrinter
	 * @throws IOException
	 */
	private void writeRow(B record, CSVPrinter csvPrinter) throws IOException {
        for(int columnCount = 0; columnCount<exportedColumnConfigs.size(); ++columnCount){
            PropertyConfig config = exportedColumnConfigs.get(columnCount);
            Object value = readFrom(config,record);
            csvPrinter.print(value);
        }
        csvPrinter.println();
	}
	/**
	 * 行计数器{@code counter}满1万控制台输出进度返回{@code true},否则返回{@code false}
	 * @param counter
	 * @param startTimeMills
	 */
	private boolean outputProgess(AtomicInteger counter, AtomicLong startTimeMills){
	    if(0 == counter.incrementAndGet() %10000){
            long ms=System.currentTimeMillis();
            SimpleLog.log("WRITE {} rows,remain {}, time cost {}s", counter.get(), Iterables.size(rows), (float)(ms-startTimeMills.get())/1000);
            startTimeMills.set(ms);
            return true;
        }
	    return false;
	}
	/**
     * 写入一条记录
	 * @param counter 计数器
	 * @param startTimeMills 开始执行时间
	 * @param sheet 
	 * @throws IOException 
     */
    private void writeRow(B record, Object target, AtomicInteger counter, AtomicLong startTimeMills) throws IOException {
        boolean output = outputProgess(counter,startTimeMills);
        if(target instanceof Sheet){
            writeRow(record,(Sheet)target);
        }else if (target instanceof CSVPrinter){
            CSVPrinter csvPrinter = (CSVPrinter)target;
            writeRow(record,csvPrinter);
            if(output){
                csvPrinter.flush();
            }
        }else {
            throw new UnsupportedOperationException("Unsupported target " + target.getClass().getName());
        }
    }
    /**
     * 从阻塞队列中获取一条记录,超时返回{@code null}
     */
    private Object pool(){
        try {
            return ((BlockingQueue<?>)rows).poll(sheetConfig.getQueueTimeout(), TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // 队列结束
            return null;
        }
	}
	/**
	 * 写入所有记录
	 * @param target 输出目标,支持{@link Sheet}和{@link CSVPrinter}
	 * @throws IOException 
	 */
	@SuppressWarnings("unchecked")
    private void writeRows(Object target) throws IOException {
	    if(!exportedColumnConfigs.isEmpty()){
	        long startTime = System.currentTimeMillis();
	        AtomicLong startTimeMills = new AtomicLong(startTime);
	        AtomicInteger counter = new AtomicInteger(0);
	        if(rows instanceof BlockingQueue){
	            /** 如果有定义记录总数,则通过行计数器判断是否中止循环 */
	            long totalRowCount = sheetConfig.getTotalRowCount();
	            for (Object record;(totalRowCount < 0 || counter.get() < totalRowCount) && null != (record = pool());){
	                if(record instanceof List){
	                    for(B bean : (List<B>)record){
	                        writeRow(bean, target, counter, startTimeMills);
	                    }
	                }else if(record.getClass().isArray()){
	                    for(B bean : (B[])record){
                            writeRow(bean, target, counter, startTimeMills);
                        }
	                }else {
	                    writeRow((B) record, target, counter, startTimeMills);
	                }
	            }
	        }else {
	            for (B record : rows) {
	                writeRow(record, target, counter, startTimeMills);
	            }
            }
	        SimpleLog.log("TOTAL WRITE {} rows, time cost {}s", 
	                counter.get(),(float)(System.currentTimeMillis() - startTime)/1000);
	    }
	}	
	
	private void autoSizeColumn(){
	    /** 行数太多时列宽调整太耗时,不调整列宽 */
	    if(sheet.getLastRowNum() > rowLimitForAutoColumnSize){
	        return ;
		}
		if(!exportedColumnConfigs.isEmpty()){
			/** 让列宽随着导出的列长自动适应 */ 
			for (int i = 0; i < exportedColumnConfigs.size(); i++) {
				/** 调整每一列宽度 */ 
				sheet.autoSizeColumn(i);
				/** 设置列宽度, x17/10适应中文宽度 */
				int newWith = sheet.getColumnWidth(i);
				if(newWith < 256*256){
					/** 设置列宽度 */
					sheet.setColumnWidth(i, newWith);
					if(sheetConfig.getMaxWidth() > 0){
						int maxWith = Math.min((int)sheetConfig.getMaxWidth() * 256,256*256);
						if (newWith > maxWith) {
							/** 限制最大宽度 */
							sheet.setColumnWidth(i, maxWith);
						}
					}
				}
			}
			autoColumnWidthForChineseChar(sheet, 0, exportedColumnConfigs.size());
		}
	}
	/**
	 * 计算字符串中中文字符的数量
	 * 参见 <a hrft="https://www.cnblogs.com/straybirds/p/6392306.html">《汉字unicode编码范围》</a>
	 * @param input
	 * @return
	 */
	private static int chineseCharCountOf(String input){
		int count = 0;//汉字数量
		if(null != input){
			String regEx = "[\\u4e00-\\u9fa5]";
			Pattern p = Pattern.compile(regEx);
			Matcher m = p.matcher(input);
			int len = m.groupCount();
			//获取汉字个数
			while (m.find()) {
				for (int i = 0; i <= len; i++) {
					count = count + 1;
				}
			}
		}
		return count;
	}
	/**
	 * 自动调整列表宽度适应中文字符串
	 * @param sheet
	 * @param startColumnNum 要调整的起始列表号
	 * @param size  要调整的列表数量
	 */
	private static void autoColumnWidthForChineseChar(Sheet sheet, int startColumnNum, int size) {    
	    for (int columnNum = 0; columnNum < size; columnNum++) {
	        /** 获取列宽 */
	        final int columnWidth = sheet.getColumnWidth(columnNum);
	        if(columnNum >= 256*256 ){
	        	/** 列宽已经超过最大列宽则放弃当前列遍历 */
	        	continue;
	        }
	        /** 新的列宽 */
	        int newWidth = columnWidth;
	        /** 遍历所有的行,查找有汉字的列计算新的最大列宽 */
	        for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
	            Row currentRow;
	            if (sheet.getRow(rowNum) == null) {
	                continue;
	            } else {
	                currentRow = sheet.getRow(rowNum);
	            }
	            if (currentRow.getCell(columnNum) != null) {
	                Cell currentCell = currentRow.getCell(columnNum);
	                if (currentCell.getCellType() == CellType.STRING) {
	                	String value = currentCell.getStringCellValue();
	                    int count = chineseCharCountOf(value);
	                    /**在该列字符长度的基础上加上汉字个数计算列宽 */
	                    int length = value.length()*256+count*256*2;
	                	/** 汉字在内容中占用的字符串 使用字符串的字节长度计算列宽 */
//	                    int length = value.getBytes().length*256;
	                    if (newWidth < length && length < 256*256) {
	                    	newWidth = length;
	                    }
	                }
	            }
	        }
	        if(newWidth != columnWidth){
	        	//设置列宽
	        	sheet.setColumnWidth(columnNum, newWidth);
	        }
	    }
	}
	private Object readFrom(PropertyConfig config,Object record){
		
		Object value = config.readFrom(record);
		String defaultValue = config.getColumnConfig().getDefaultValue();
		if(null== value && !defaultValue.isEmpty()){
			return defaultValue;
		}
		String readConverterExp = config.getColumnConfig().getReadConverterExp();
		if(!readConverterExp.isEmpty()){
			return MapExpression.convertByExp( value, readConverterExp, ",");
		}
		Class<?> handlerClass = config.getColumnConfig().getHandler();
		if(handlerClass != ExcelHandlerAdapter.class && ExcelHandlerAdapter.class.isAssignableFrom(handlerClass)){
			try {
				String[] args = config.getColumnConfig().getArgs();
				return ((ExcelHandlerAdapter)handlerClass.newInstance()).format(value, args);
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return isEmpty(value)?null : value;
	}
	
	private String keyOf(PropertyConfig columnConfig,Object value){
		return  "data_" + columnConfig.getHorizontalAlign() + "-" + columnConfig.getColor()+ "-" + columnConfig.getFillColor()+ "-" + columnConfig.getDataFormat(value);
	}
	
	private CellStyle styleOf(PropertyConfig propertyConfig, Object value){
		String key = keyOf(propertyConfig,value);
		CellStyle style = styles.get(key);
		if(null == style){
			style = createCellStyle(getWorkbook(),propertyConfig, value);
			styles.put(key, style);
		}
		return style;
	}
	
    /**
     * 根据Excel配置创建表格样式
     * 
     * @param wb 工作薄对象
     * @return 自定义样式列表
     */
    private void createColumnStyles(Workbook wb)
    {
        sheetConfig.getColumnConfigs().forEach(cfg->styleOf(cfg, null));
    }
    
    private CellStyle createCellStyle(Workbook wb,PropertyConfig cfg, Object value){
        CellStyle style = wb.createCellStyle();
        style = wb.createCellStyle();
        style.setAlignment(null == cfg ? sheetConfig.getHorizontalAlign() : cfg.getHorizontalAlign());
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setBorderRight(BorderStyle.THIN);
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderLeft(BorderStyle.THIN);
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderTop(BorderStyle.THIN);
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderBottom(BorderStyle.THIN);
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        Font dataFont = wb.createFont();
        dataFont.setFontName(sheetConfig.getFontName());
        dataFont.setFontHeightInPoints(sheetConfig.getFontHeight());
        dataFont.setColor(null == cfg ? sheetConfig.getFontColor().index : cfg.getColor().index);
        style.setFont(dataFont);
    	style.setFillForegroundColor(null == cfg ? sheetConfig.getFillColor().index : cfg.getFillColor().index);
    	style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    	if(null != cfg){
    		String fmt = cfg.getDataFormat(value);
    		if(!fmt.isEmpty()){
    			Class<?> type = value.getClass();
    			type = Primitives.wrap(type);
    			if(Date.class.isAssignableFrom(type)|| ChronoLocalDate.class.isAssignableFrom(type) || ChronoLocalDateTime.class.isAssignableFrom(type)){
    				CreationHelper createHelper = getWorkbook().getCreationHelper();
    				short dateFormat = createHelper.createDataFormat().getFormat(fmt);
    				style.setDataFormat(dateFormat);
    			}else if (Long.class.equals(type) || Integer.class.equals(type)|| Short.class.equals(type)|| Byte.class.equals(type)) {
    				DataFormat format = getWorkbook().createDataFormat();
    				style.setDataFormat(format.getFormat(fmt));
    			}
    		}
    	}
        return style;
        
    }
    private CellStyle createDefaultCellStyle(Workbook wb){
    	return createCellStyle(wb,null, null);
    }
    /**
     * 创建表格样式
     * 
     * @param wb 工作薄对象
     * @return 样式列表
     */
    private void createStyles(Workbook wb)
    {
        // 写入各条记录,每条记录对应excel表中的一行
        CellStyle style = wb.createCellStyle();
        style.setAlignment(sheetConfig.getTitleHorizontalAlign());
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        Font titleFont = wb.createFont();
        titleFont.setFontName(sheetConfig.getTitleFontName());
        titleFont.setFontHeightInPoints(sheetConfig.getTitleFontHeight());
        titleFont.setColor(sheetConfig.getTitleFontColor().index);
        titleFont.setBold(true);
        style.setFont(titleFont);
        style.setFillForegroundColor(sheetConfig.getTitleFillColor().index);
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        styles.put("title", style);

        styles.put("data", createDefaultCellStyle(getWorkbook()));

        style = wb.createCellStyle();
        style.cloneStyleFrom(styles.get("data"));
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        Font headerFont = wb.createFont();
        headerFont.setFontName(sheetConfig.getHeaderFontName());
        headerFont.setFontHeightInPoints(sheetConfig.getHeaderFontHeight());
        headerFont.setBold(sheetConfig.isFirstBold());
        headerFont.setColor(sheetConfig.getHeaderFontColor().index);
        style.setFont(headerFont);
        style.setFillBackgroundColor(sheetConfig.getHeaderFillColor().index);
        styles.put("header", style);

        style = wb.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        Font totalFont = wb.createFont();
        totalFont.setFontName(sheetConfig.getFontName());
        totalFont.setFontHeightInPoints(sheetConfig.getFontHeight());
        style.setFont(totalFont);
        style.setFillForegroundColor(sheetConfig.getFillColor().index);
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        styles.put("total", style);

        createColumnStyles(wb);

    }
    
    /**
     * 创建一个工作簿
     */
    private void createSheet()
    {
    	if(null == this.workbook){
    		this.workbook = new XSSFWorkbook();
    	}
    	if(null == this.sheet){
    		this.sheet = workbook.createSheet(sheetConfig.getSheetName());
    	}
        createStyles(workbook);
    }
    
	/**
	 * 返回Excel表输出配置({@link SheetConfig})实例,可以通过此实例修改输出配置
	 */
	public SheetConfig getSheetConfig() {
		return sheetConfig;
	}
	public SheetGenerator<B> setSheetConfig(SheetConfig sheetConfig) {
		if(null != sheetConfig){
			this.sheetConfig = sheetConfig;
		}
		return this;
	}
	public SheetGenerator<B> setRowLimitForAutoColumnSize(int rowLimitForAutoColumnSize) {
	    if(rowLimitForAutoColumnSize > 0){
	        this.rowLimitForAutoColumnSize = rowLimitForAutoColumnSize;
	    }
        return this;
    }
    @Override
	protected void write() throws IOException {
		createSheet();
		exportedColumnConfigs = Lists.newArrayList(sheetConfig.getExportedColumnConfigs());
		
		createTitle();
		writeHeader(sheet);
		writeRows(sheet);
		autoSizeColumn();
		
	}
    protected void write(CSVPrinter csvPrinter) throws IOException {
        createSheet();
        /** 创建一行,作为后续所有行的临时写入Row对象 */
        sheet.createRow(rownum);
        exportedColumnConfigs = Lists.newArrayList(sheetConfig.getExportedColumnConfigs());
        writeHeader(csvPrinter);
        writeRows(csvPrinter);
        csvPrinter.flush();
        
    }
    @Override
    protected void write(OutputStream outputStream) throws IOException {
        /** csvPrinter 不需要close,outputStream由调用层关闭 */
        CSVPrinter csvPrinter = new CSVPrinter(new PrintWriter(outputStream), CSVFormat.DEFAULT);
        write(csvPrinter);
        outputStream.flush();
    }
	@Override
	protected Workbook getWorkbook() {
		return workbook;
	}
	SheetGenerator<B> setWorkbook(Workbook workbook) {
		this.workbook = workbook;
		return this;
	}
}
